#include <linux/list.h>
#include "../inc/sched.h"
#include <linux/sched/deadline.h>
#include <linux/rbtree.h>

#define __node_2_dle(node) \
	rb_entry((node), struct sched_dl_entity, rb_node)

static bool dl_server(struct sched_dl_entity *dl_se)
{
	return dl_se->dl_server;
}

static inline struct task_struct *dl_task_of(struct sched_dl_entity *dl_se)
{
	return container_of(dl_se, struct task_struct, dl);
}

static inline struct rq *rq_of_dl_se(struct sched_dl_entity *dl_se)
{
	struct rq *rq = dl_se->rq;

	if (!dl_server(dl_se))
		rq = task_rq(dl_task_of(dl_se));

	return rq;
}

static inline struct dl_rq *dl_rq_of_se(struct sched_dl_entity *dl_se)
{
	return &rq_of_dl_se(dl_se)->dl;
}

static inline bool __dl_less(struct rb_node *a, const struct rb_node *b)
{
	return dl_time_before(__node_2_dle(a)->deadline, __node_2_dle(b)->deadline);
}

static inline void inc_dl_tasks(struct sched_dl_entity *dl_se, struct dl_rq *dl_rq)
{
}

static void __enqueue_dl_entity(struct sched_dl_entity *dl_se)
{
	struct dl_rq *dl_rq = dl_rq_of_se(dl_se);

	WARN_ON_ONCE(!RB_EMPTY_NODE(&dl_se->rb_node));

	rb_add_cached(&dl_se->rb_node, &dl_rq->root, __dl_less);

	inc_dl_tasks(dl_se, dl_rq);
}

static void enqueue_dl_entity(struct sched_dl_entity *dl_se, int flags)
{

	__enqueue_dl_entity(dl_se);
}

static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags)
{
	if (!dl_prio(p->normal_prio))
	{
		p->dl.dl_throttled = 0;
	}
	else
	{
		enqueue_dl_entity(&p->dl, flags);
	}
}

/*
 * Only called when both the current and waking task are -deadline
 * tasks.
 */
static void wakeup_preempt_dl(struct rq *rq, struct task_struct *p,
							  int flags)
{
	if (dl_entity_preempt(&p->dl, &rq->donor->dl))
	{
		resched_curr(rq);
		return;
	}
}

static struct sched_dl_entity *pick_next_dl_entity(struct dl_rq *dl_rq)
{
	struct rb_node *left = rb_first_cached(&dl_rq->root);

	if (!left)
		return NULL;

	return __node_2_dle(left);
}

static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64 delta_exec)
{
	//TODO
}

/*
 * __pick_next_task_dl - Helper to pick the next -deadline task to run.
 * @rq: The runqueue to pick the next task from.
 */
static struct task_struct *__pick_task_dl(struct rq *rq)
{
	struct sched_dl_entity *dl_se;
	struct dl_rq *dl_rq = &rq->dl;
	struct task_struct *p;

again:
	if (!sched_dl_runnable(rq))
		return NULL;

	dl_se = pick_next_dl_entity(dl_rq);
	WARN_ON_ONCE(!dl_se);

	if (dl_server(dl_se))
	{
		p = dl_se->server_pick_task(dl_se);
		if (!p)
		{
			if (dl_server_active(dl_se))
			{
				dl_se->dl_yielded = 1;
				update_curr_dl_se(rq, dl_se, 0);
			}
			goto again;
		}
		rq->dl_server = dl_se;
	}
	else
	{
		p = dl_task_of(dl_se);
	}

	return p;
}

static struct task_struct *pick_task_dl(struct rq *rq)
{
	return __pick_task_dl(rq);
}

static void init_dl_rq(struct rq *rq)
{
    struct dl_rq *dl_rq = &rq->dl;

	dl_rq->root = RB_ROOT_CACHED;
}

DEFINE_SCHED_CLASS(dl) = {
	.enqueue_task = enqueue_task_dl,
	.pick_task = pick_task_dl,

	.wakeup_preempt = wakeup_preempt_dl,

	.init_rq = init_dl_rq,
};

/*
 * Tells if entity @a should preempt entity @b.
 */
bool dl_entity_preempt(const struct sched_dl_entity *a,
					   const struct sched_dl_entity *b)
{
	return dl_time_before(a->deadline, b->deadline);
}

void dl_server_stop(struct sched_dl_entity *dl_se)
{
}
